/**
 * 添加拦截器:
 *      拦截 request 请求
 *      拦截 uploadFile 文件上传
 *
 * TODO:
 *      1.非 http 开头需拼接地址
 *      2.请求超时
 *      3.添加小程序端请求头标识
 *      4.添加 token 请求头标识
 */
import { useMemberStore } from '@/stores'

const baseURL = 'https://pcapi-xiaotuxian-front-devtest.itheima.net' // 服务器地址

const menberStore = useMemberStore()

const httpInterceptor = {
  // 拦截前触发
  // options: 接收uni.request的参数，将鼠标悬停在uni.request上，会提示出参数类型，cv过来即可
  invoke: (options: UniApp.RequestOptions) => {
    // TODO: 非http开头需拼接地址（如果是http开头，则已经是完整的地址了）
    if (!options.url.startsWith('http')) {
      options.url = baseURL + options.url
    }
    // TODO: 请求超时,默认是60s
    options.timeout = 10000
    // console.log('请求拦截器', options) // 打印请求参数,在微信开发者工具的调试器中查看
    // TODO: 添加小程序端请求头标识
    options.header = {
      ...options.header, // 保留请求时传过来的请求头,如：uni.request({ url: '/api/test', method: 'GET', header: { 'Content-Type': 'application/json' } })
      'source-client': 'miniapp', // 小程序端标识
    }
    // TODO: 添加 token 请求头标识
    // (请求成功后，会返回一个 token，需要将 token 添加到请求头中。请求成功后的用户信息自行保存到了memberStore的profile中的，所以从memberStore中获取token即可)
    const token = menberStore.profile?.token // token可能为undefined，所以使用 ?. 可选链
    if (token) {
      options.header['Authorization'] = token
    }
  },
}

// 添加拦截器(拦截的多个接口，都是使用同一个配置)
uni.addInterceptor('request', httpInterceptor) // 当使用 uni.request 发起请求时，会触发拦截器
uni.addInterceptor('uploadFile', httpInterceptor) // 当使用 uni.uploadFile 发起请求时，会触发拦截器

// uni.request 使用示例：uni.request({ url: '/api/test', method: 'GET' })

/**
 * 请求函数
 * 借鉴axios：返回一个promise，同时配合 async、await 更好的获取返回的数据。
 * uni.request 不支持添加类型，需要自己封装一个函数，在函数中添加泛型来确定使用时的类型。
 * @param UniApp.Requestoptions
 * @returns Promise
 *  1.返回 Promise 对象(用来包装 uni.request)
 *  2.请求成功
 *    -1 提取核心数据 res.data
 *    -2 添加类型，支持泛型
 *  3.请求失败
 *    -1 网络错误 ->提示用户换网络
 *    -2 401错误清理用户信息，跳转到登录页
 *    -3 其他错误 ->根据后端错误信息轻提示
 */
// 已知后端接口返回的数据格式后，就可以定义一个接口来更精确描述后端返回的数据格式
// 后端返回的数据格式：{ code: number, msg: string, result: T }
// 因为此数据结构里，只有 result 的类型会动态改变，所以使用泛型 T 来描述 result 的类型
// T 是一个泛型，表示此接口接收一个泛型T，T的类型由调用此接口时传入,如: Data<T>
interface Data<T> {
  code: number
  msg: string
  result: T
}

// 参数options是给uni.request使用的所以使用鼠标悬浮uni.request时提示的类型，声明一个泛型T给返回的Promise使用
// <T> 表示调用 http 时可以接收一个类型给函数内部使用，如：http<string>()，此时T就是string类型，函数内部的T就都表示string类型
export const http = <T>(options: UniApp.RequestOptions): Promise<Data<T>> => {
  // 返回 Promise 对象 <定义promise返回值的类型> （此处定义promise返回值的类型和上一行定义promise返回值的类型二选一即可）
  return new Promise<Data<T>>((resolve, reject) => {
    // 里面包一个 uni.request
    uni.request({
      ...options,
      // 服务器响应成功
      success: (res: UniApp.RequestSuccessCallbackResult) => {
        const { statusCode } = res
        if (statusCode >= 200 && statusCode < 300) {
          // 请求成功
          resolve(res.data as Data<T>) // 类型断言为和上面指定的promise返回值类型一样
        } else if (statusCode === 401) {
          // token 错误清理用户信息，跳转到登录页
          menberStore.clearProfile()
          uni.navigateTo({ url: '/pages/login/login' }) // 路径在pages.json中配置和查看
          reject(res)
        } else {
          // 其他错误 ->根据后端错误信息轻提示
          uni.showToast({ icon: 'none', title: (res.data as Data<T>).msg || '请求失败' })
          reject(res)
        }
      },
      // 服务器响应失败
      fail: (err) => {
        // 网络错误 ->提示用户换网络、服务器错误 ->提示用户稍后再试
        uni.showToast({ icon: 'none', title: '网络错误，换个网络试试' })
        reject(err)
      },
    })
  })
}

// 泛型T的类型传递过程：http<string>() => const http = <T>(){} => Promise<Data<T>>(){} => interface Data<T>
